//
//	File Name   :	3dsloader.cpp
//	Description :	3dsloader implementation file.
//	Author		  :	William McVicar
//  Mail		    :	mcvicar.william@gmail.com
//

//  Precompiled Header
#include "CommonDefines.h"

//  Library Includes

//  Local Includes
#include "../ProteinFramework.h"
#include "../IFileSystem.h"

//  This includes
#include "3dsloader.h"

//	Macros

//  Static Variables

//  Static Function Prototypes

//  Implementation

namespace Protein
{

C3DSLoader::C3DSLoader()
: m_pCurrentChunk( new tChunk )
, m_pTempChunk( new tChunk )
{

}

C3DSLoader::~C3DSLoader()
{

}

bool C3DSLoader::Import3DS( t3DModel* _pModel, const c8* _szFilename )
{
	c8 szMessage[255] = {0};

	if( !fileSystem().Open( _szFilename, g_kBINARY_READ_ONLY, &m_pFilePointer ) )
	{
		sprintf(szMessage, "Unable to find the file: %s!", _szFilename);
		MessageBoxA(NULL, szMessage, "Error", MB_OK);
		return false;
	}

	ReadChunk( m_pCurrentChunk );

	if( m_pCurrentChunk->usID != PRIMARY )
	{
		sprintf(szMessage, "Unable to load PRIMARY chunck from file: %s!", _szFilename);
		MessageBoxA(NULL, szMessage, "Error", MB_OK);
		return false;
	}

	ProcessNextChunk( _pModel, m_pCurrentChunk );
	//ComputeNormals( _pModel );
	CleanUp();

	return true;
}

i32 C3DSLoader::GetString(c8* _szBuffer)
{
	i32 index = 0;
	
	fread(_szBuffer, 1, 1, m_pFilePointer);
	
	while (*(_szBuffer + index++) != 0) 
	{
		fread(_szBuffer + index, 1, 1, m_pFilePointer);
	}
	
	return strlen(_szBuffer) + 1;
}

void C3DSLoader::ReadChunk(tChunk* _pChunk)
{
	_pChunk->uiBytesRead = fread(&_pChunk->usID, 1, 2, m_pFilePointer);
	
	_pChunk->uiBytesRead += fread(&_pChunk->uiLength, 1, 4, m_pFilePointer);
}

void C3DSLoader::ProcessNextChunk( t3DModel* _pModel, tChunk* _pPreviousChunk)
{
	t3DObject newObject = {0};					 
	tMaterialInfo newTexture = {0};				
	u32 version = 0;					
	i32 buffer[50000] = {0};					
	
	m_pCurrentChunk = new tChunk;
	
	while (_pPreviousChunk->uiBytesRead < _pPreviousChunk->uiLength)
	{
		ReadChunk(m_pCurrentChunk);
		
		switch (m_pCurrentChunk->usID)
		{
		case VERSION:							
			
			m_pCurrentChunk->uiBytesRead += fread(&version, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			if (version > 0x03)
				MessageBoxA(NULL, "This 3DS file is over version 3 so it may load incorrectly", "Warning", MB_OK);
			break;
			
		case OBJECTINFO:						
			ReadChunk(m_pTempChunk);
			
			m_pTempChunk->uiBytesRead += fread(&version, 1, m_pTempChunk->uiLength - m_pTempChunk->uiBytesRead, m_pFilePointer);
			
			m_pCurrentChunk->uiBytesRead += m_pTempChunk->uiBytesRead;
			
			ProcessNextChunk(_pModel, m_pCurrentChunk);
			break;
			
		case MATERIAL:						
			_pModel->iNumOfMaterials++;
			
			_pModel->materials.push_back(newTexture);
			
			ProcessNextMaterialChunk(_pModel, m_pCurrentChunk);
			break;
			
		case OBJECT:							
			_pModel->iNumOfObjects++;
			
			_pModel->object.push_back(newObject);
			
			memset(&(_pModel->object[_pModel->iNumOfObjects - 1]), 0, sizeof(t3DObject));
			
			if( _pModel->iNumOfMaterials > 0 )
				m_pCurrentChunk->uiBytesRead += GetString(_pModel->object[_pModel->iNumOfMaterials - 1].szName);
			
			ProcessNextObjectChunk(_pModel, &(_pModel->object[_pModel->iNumOfObjects - 1]), m_pCurrentChunk);
			break;
			
		case EDITKEYFRAME:
			
			m_pCurrentChunk->uiBytesRead += fread(buffer, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			break;
			
		default: 
			
			m_pCurrentChunk->uiBytesRead += fread(buffer, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			break;
		}
		
		_pPreviousChunk->uiBytesRead += m_pCurrentChunk->uiBytesRead;
	}
	
	delete m_pCurrentChunk;
	m_pCurrentChunk = _pPreviousChunk;
}

void C3DSLoader::ProcessNextObjectChunk(t3DModel* _pModel, t3DObject* _pObject, tChunk* _pPreviousChunk)
{
	int buffer[50000] = {0};					
	
	m_pCurrentChunk = new tChunk;
	
	while (_pPreviousChunk->uiBytesRead < _pPreviousChunk->uiLength)
	{
		ReadChunk(m_pCurrentChunk);
		
		switch (m_pCurrentChunk->usID)
		{
		case OBJECT_MESH:		
			ProcessNextObjectChunk(_pModel, _pObject, m_pCurrentChunk);
			break;
			
		case OBJECT_VERTICES:			 
			ReadVertices(_pObject, m_pCurrentChunk);
			break;
			
		case OBJECT_FACES:				 
			ReadVertexIndices(_pObject, m_pCurrentChunk);
			break;
			
		case OBJECT_MATERIAL:			 
			ReadObjectMaterial(_pModel, _pObject, m_pCurrentChunk);			
			break;
			
		case OBJECT_UV:					
			ReadUVCoordinates(_pObject, m_pCurrentChunk);
			break;
			
		default: 
			m_pCurrentChunk->uiBytesRead += fread(buffer, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			break;
		}
		
		_pPreviousChunk->uiBytesRead += m_pCurrentChunk->uiBytesRead;
	}
	
	delete m_pCurrentChunk;
	m_pCurrentChunk = _pPreviousChunk;
}

void C3DSLoader::ProcessNextMaterialChunk(t3DModel* _pModel, tChunk* _pPreviousChunk )
{
	int buffer[50000] = {0};					  
	m_pCurrentChunk = new tChunk;
	
	while (_pPreviousChunk->uiBytesRead < _pPreviousChunk->uiLength)
	{
		ReadChunk(m_pCurrentChunk);
		
		switch (_pModel, m_pCurrentChunk->usID)
		{
		case MATNAME:						 
			m_pCurrentChunk->uiBytesRead += fread(_pModel->materials[_pModel->iNumOfMaterials - 1].szName, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			break;
			
		case MATDIFFUSE:					 
			ReadColorChunk(&(_pModel->materials[_pModel->iNumOfMaterials - 1]), m_pCurrentChunk);
			break;
			
		case MATMAP:						 
			ProcessNextMaterialChunk(_pModel, m_pCurrentChunk);
			break;
			
		case MATMAPFILE:						 
			m_pCurrentChunk->uiBytesRead += fread(_pModel->materials[_pModel->iNumOfMaterials - 1].szFile, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			break;
			
		default:  
			m_pCurrentChunk->uiBytesRead += fread(buffer, 1, m_pCurrentChunk->uiLength - m_pCurrentChunk->uiBytesRead, m_pFilePointer);
			break;
		}
		_pPreviousChunk->uiBytesRead += m_pCurrentChunk->uiBytesRead;
	}
	
	delete m_pCurrentChunk;
	m_pCurrentChunk = _pPreviousChunk;
}

void C3DSLoader::ReadColorChunk(tMaterialInfo* _pMaterial, tChunk* _pChunk)
{
	ReadChunk(m_pTempChunk);
	m_pTempChunk->uiBytesRead += fread(_pMaterial->colour, 1, m_pTempChunk->uiLength - m_pTempChunk->uiBytesRead, m_pFilePointer);
	_pChunk->uiBytesRead += m_pTempChunk->uiBytesRead;

}

void C3DSLoader::ReadVertices(t3DObject* _pObject, tChunk* _pPreviousChunk )
{
	_pPreviousChunk->uiBytesRead += fread(&(_pObject->iNumVerts), 1, 2, m_pFilePointer);
	
	_pObject->pVerts = new Vector3f [_pObject->iNumVerts];
	memset(_pObject->pVerts, 0, sizeof(Vector3f) * _pObject->iNumVerts);
	
	_pPreviousChunk->uiBytesRead += fread(_pObject->pVerts, 1, _pPreviousChunk->uiLength - _pPreviousChunk->uiBytesRead, m_pFilePointer);
}

void C3DSLoader::ReadVertexIndices(t3DObject* _pObject, tChunk* _pPreviousChunk )
{
	unsigned short index = 0;					 
	_pPreviousChunk->uiBytesRead += fread(&_pObject->iNumFaces, 1, 2, m_pFilePointer);
	
	_pObject->pFaces = new tFace [_pObject->iNumFaces];
	memset(_pObject->pFaces, 0, sizeof(tFace) * _pObject->iNumFaces);
	
	
	for(int i = 0; i < _pObject->iNumFaces; i++)
	{
		for(int j = 0; j < 4; j++)
		{
			_pPreviousChunk->uiBytesRead += fread(&index, 1, sizeof(index), m_pFilePointer);
			
			if(j < 3)
			{
				_pObject->pFaces[i].iVertIndex[j] = index;
			}
		}
	}
}

void C3DSLoader::ReadUVCoordinates(t3DObject* _pObject, tChunk* _pPreviousChunk )
{
	_pPreviousChunk->uiBytesRead += fread(&_pObject->iNumTexVertex, 1, 2, m_pFilePointer);
	
	_pObject->pTexVerts = new Vector2f [_pObject->iNumTexVertex];
	
	_pPreviousChunk->uiBytesRead += fread(_pObject->pTexVerts, 1, _pPreviousChunk->uiLength - _pPreviousChunk->uiBytesRead, m_pFilePointer);

}

void C3DSLoader::ReadObjectMaterial(t3DModel* _pModel, t3DObject* _pObject, tChunk* _pPreviousChunk)
{
	char strMaterial[255] = {0};			
	int buffer[50000] = {0};				
	
	_pPreviousChunk->uiBytesRead += GetString(strMaterial);
	
	for(int i = 0; i < _pModel->iNumOfMaterials; i++)
	{
		if(strcmp(strMaterial, _pModel->materials[i].szName) == 0)
		{
			_pObject->iMaterialID = i;
			
			if(strlen(_pModel->materials[i].szFile) > 0) {
				
				_pObject->bHasTexture = true;
			}	
			break;
		}
	}
	
	_pPreviousChunk->uiBytesRead += fread(buffer, 1, _pPreviousChunk->uiLength - _pPreviousChunk->uiBytesRead, m_pFilePointer);
}

void C3DSLoader::ComputeNormals(t3DModel *_pModel)
{
	Vector3f vVector1, vVector2, vNormal, vPoly[3];
	
	if(_pModel->iNumOfObjects <= 0)
		return;
	
	for(int index = 0; index < _pModel->iNumOfObjects; ++index)
	{
		t3DObject Object = _pModel->object[index];
		
		Vector3f *pNormals		= new Vector3f [Object.iNumFaces];
		Vector3f *pTempNormals	= new Vector3f [Object.iNumFaces];
		Object.pNormals			= new Vector3f [Object.iNumVerts];
		
		for( i32 i = 0; i < Object.iNumFaces; ++i )
		{												
			vPoly[0] = Object.pVerts[Object.pFaces[i].iVertIndex[0]];
			vPoly[1] = Object.pVerts[Object.pFaces[i].iVertIndex[1]];
			vPoly[2] = Object.pVerts[Object.pFaces[i].iVertIndex[2]];
			
			vVector1 = vPoly[0] - vPoly[2];				
			vVector2 = vPoly[2] - vPoly[1];				
			
			vNormal  = vVector1.cross( vVector2 );		
			pTempNormals[i] = vNormal;					
			vNormal.normalise();				
			
			pNormals[i] = vNormal;						
		}
		
		Vector3f vSum(0.0, 0.0, 0.0);
		Vector3f vZero(0.0, 0.0, 0.0);
		int shared=0;
		
		for (int i = 0; i < Object.iNumVerts; i++)			
		{
			for (int j = 0; j < Object.iNumFaces; j++)	
			{												
				if (Object.pFaces[j].iVertIndex[0] == i || 
					Object.pFaces[j].iVertIndex[1] == i || 
					Object.pFaces[j].iVertIndex[2] == i)
				{
					vSum = vSum + pTempNormals[j];			
					++shared;								
				}
			}      
			
			Object.pNormals[i] = vSum / float(-shared) ;
			
      Object.pNormals[i] = Object.pNormals[i].normalised();	
			
			vSum = vZero;									 
			shared = 0;										
		}
		
		delete [] pTempNormals;
		delete [] pNormals;
	}
}

void C3DSLoader::CleanUp()
{
	fileSystem().Close( m_pFilePointer );
	delete m_pCurrentChunk;
	delete m_pTempChunk;
}

}